#ifndef __HD44780_INSTRUCTIONS_H__
#define __HD44780_INSTRUCTIONS_H__

#include <stdbool.h>
#include <stdlib.h>
#include "hd44780_types.h"
#include "hd44780_interface.h"

#define INLINE inline

typedef enum {
	HD44780_STAT_BUSY,
	HD44780_STAT_FREE,
} HD44780_STAT;

typedef enum {
	ZERO,
	ONE,
} BINARY ;


/* position(adderss) into memory(virtual memory space) mapped of data reg */
volatile unsigned char * dr_addr_p ;

/* position(address) into memory(virtual memory space) mapped of instruction reg */
volatile unsigned char * ir_addr_p ;

#define DELAY_SHORT()	HD44780_DELAY( 1000 );
#define DELAY_LONG()	HD44780_DELAY( 150*300 ); 

INLINE void HD44780_DELAY( unsigned int count ){

	while( count ){
		count--;
	}
	return;
}

INLINE void WRITE_OCTER( volatile unsigned char * addr , OCTER data ){
	*addr = data.byte;
	return;
}

INLINE OCTER READ_OCTER( volatile unsigned char * addr ){
	return (OCTER)*addr;
}

#ifdef HD44780_HAS_DDR

volatile unsigned char * dr_ddr_addr_p = NULL;
volatile unsigned char * ir_ddr_addr_p = NULL;

INLINE void HD44780_SEND_DR_DDR( OCTER ctl ){
	WRITE_OCTER( dr_ddr_addr_p , ctl );
	return;
}

INLINE void HD44780_SEND_IR_DDR( OCTER ctl ){

	OCTER ctl_tmp = READ_OCTER( ir_ddr_addr_p  );
	BIT_D_E_SET ( ctl_tmp , ctl.bits.BIT_D_E  );
	BIT_D_RS_SET( ctl_tmp , ctl.bits.BIT_D_RS );
	BIT_D_RW_SET( ctl_tmp , ctl.bits.BIT_D_RW );
	WRITE_OCTER( ir_ddr_addr_p , ctl_tmp );
	return;
}

INLINE void HD44780_SET_DR_READED( void ){

	OCTER ctl = {0x00};
	HD44780_SEND_DR_DDR(ctl);
	return;
}
INLINE void HD44780_SET_DR_WRITED( void ){

	OCTER ctl = {0xff};
	HD44780_SEND_DR_DDR(ctl);
	return;
}
INLINE void HD44780_SET_IR_READED( void ){

	OCTER ctl = {0};
	BIT_D_E_SET ( ctl , BIT_D_E_READ  );
	BIT_D_RS_SET( ctl , BIT_D_RS_READ );
	BIT_D_RW_SET( ctl , BIT_D_RW_READ );
	HD44780_SEND_IR_DDR(ctl);
	return;
}
INLINE void HD44780_SET_IR_WRITED( void ){

	OCTER ctl = {0};
	BIT_D_E_SET ( ctl , BIT_D_E_WRITE  );
	BIT_D_RS_SET( ctl , BIT_D_RS_WRITE );
	BIT_D_RW_SET( ctl , BIT_D_RW_WRITE );
	HD44780_SEND_IR_DDR(ctl);
	return;
}
#endif

INLINE OCTER HD44780_READ_DR( void ){
	return READ_OCTER( dr_addr_p );
}

INLINE OCTER HD44780_READ_IR( void ){
	return READ_OCTER( ir_addr_p );
}

INLINE void HD44780_SEND_DR( OCTER data ){

	WRITE_OCTER( dr_addr_p , data );
	DELAY_SHORT();
	return;
}

INLINE void HD44780_SEND_IR( OCTER cmd ){

	WRITE_OCTER( ir_addr_p , cmd );
	DELAY_SHORT(); 
	return;
}

INLINE HD44780_STAT HD44780_FUNC_read_bf_ac( void ){

	HD44780_SET_DR_READED();

	OCTER ctl = HD44780_READ_IR();
	BIT_RS_SET( ctl , BIT_RS_INSTRUCTION );
	BIT_RW_SET( ctl , BIT_RW_READ );

	BIT_E_SET( ctl , BIT_E_DISABLE );
	HD44780_SEND_IR( ctl );
	DELAY_SHORT();

	BIT_E_SET( ctl , BIT_E_ENABLE );
	HD44780_SEND_IR( ctl );
	DELAY_SHORT();

	OCTER data = HD44780_READ_DR();
//	printf("read_bf_ac: %02X \n", data.byte);
	DELAY_SHORT();

	BIT_E_SET( ctl , BIT_E_DISABLE );
	HD44780_SEND_IR( ctl );
	DELAY_SHORT();


	HD44780_SET_DR_WRITED();

	if( data.bits.b7 == 1 ){
		return HD44780_STAT_BUSY;
	}
	return HD44780_STAT_FREE;
}

INLINE void HD44780_HANDLE_INS( OCTER cmd ){

	while( HD44780_STAT_BUSY == HD44780_FUNC_read_bf_ac() ){
		DELAY_SHORT();
	}
	/* keep other bits */
	OCTER cmd_tmp = HD44780_READ_IR();
	
	BIT_RS_SET( cmd_tmp , cmd.bits.BIT_RS );
	BIT_RW_SET( cmd_tmp , cmd.bits.BIT_RW );

	/* toggle pin_e to enable this instrction */
	BIT_E_SET( cmd_tmp , BIT_E_ENABLE );
	HD44780_SEND_IR( cmd_tmp );

	BIT_E_SET( cmd_tmp , BIT_E_DISABLE );
	HD44780_SEND_IR( cmd_tmp );

	return; 
}

INLINE void HD44780_FUNC_clear_display( void ){

	OCTER data = {0};
	data.bits.b0 = 1;
	HD44780_SEND_DR( data );

	OCTER ctl = {0};
	HD44780_HANDLE_INS( ctl );
	DELAY_LONG();
	return;
}

INLINE void HD44780_FUNC_return_home( void ){

	OCTER data = {0};
	data.bits.b1 = 1;
	HD44780_SEND_DR( data );
	
	OCTER ctl = {0};
	HD44780_HANDLE_INS( ctl );
	return;
}

INLINE void HD44780_FUNC_entry_mode_set( BINARY I_D , BINARY S ){

	OCTER data = {0};
	data.bits.b0 = S;
	data.bits.b1 = I_D; 
	data.bits.b2 = 1;
	HD44780_SEND_DR( data );
	
	OCTER ctl = {0};
	HD44780_HANDLE_INS( ctl );
	return;
}

INLINE void HD44780_FUNC_display_on_off_control( BINARY D , BINARY C , BINARY B ){

	OCTER data = {0};
	data.bits.b0 = B;
	data.bits.b1 = C;
	data.bits.b2 = D;
	data.bits.b3 = 1;
	HD44780_SEND_DR( data );
		
	OCTER ctl = {0};
	HD44780_HANDLE_INS( ctl );
	return;
}

INLINE void HD44780_FUNC_cursor_display_shift( BINARY S_C , BINARY R_L ){

	OCTER data = {0};
	data.bits.b2 = R_L;
	data.bits.b3 = S_C;
	data.bits.b4 = 1;
	HD44780_SEND_DR( data );

	OCTER ctl = {0};
	HD44780_HANDLE_INS( ctl );
	return;
}

INLINE void HD44780_FUNC_function_set( BINARY DL , BINARY N , BINARY F ){

	OCTER data = {0};
	data.bits.b2 = F;
	data.bits.b3 = N;
	data.bits.b4 = DL;
	data.bits.b5 = 1;
	HD44780_SEND_DR( data );
	
	OCTER ctl = {0};
	HD44780_HANDLE_INS( ctl );
	return;
}

INLINE void HD44780_FUNC_set_cgram_addr( CG_ADDR cd_addr ){

	OCTER data = {0};
	data = cd_addr.CGA;
	data.bits.b6 = 1;
	HD44780_SEND_DR( data );

	OCTER ctl = {0};
	HD44780_HANDLE_INS( ctl );
	return;
}

INLINE void HD44780_FUNC_set_ddram_addr( DD_ADDR dd_addr ){

	OCTER data = {0};
	data = dd_addr.DDA;
	data.bits.b7 = 1;
	HD44780_SEND_DR( data );	

	OCTER ctl = {0};
	HD44780_HANDLE_INS( ctl );
	return;
}


INLINE void HD44780_FUNC_write_data( OCTER data ){
	
	HD44780_SEND_DR( data );
	
	OCTER ctl = {0};
	BIT_RS_SET(ctl , BIT_RS_DATA );
	HD44780_HANDLE_INS( ctl );

	return;
}

INLINE OCTER HD44780_FUNC_read_data( void ){

	while( HD44780_STAT_BUSY == HD44780_FUNC_read_bf_ac() ){
		DELAY_SHORT();
	}
	HD44780_SET_DR_READED();
	DELAY_SHORT();

	OCTER ctl = HD44780_READ_IR();
	BIT_RS_SET( ctl , BIT_RS_DATA );
	BIT_RW_SET( ctl , BIT_RW_READ );

	/* toggle pin_e to enable this instrction */
	BIT_E_SET( ctl , BIT_E_DISABLE );
	HD44780_SEND_IR( ctl );

	BIT_E_SET( ctl , BIT_E_ENABLE );
	HD44780_SEND_IR( ctl );
	DELAY_SHORT();
	
	/* read the data from ddram */
	OCTER data = HD44780_READ_DR();
	DELAY_SHORT();

	BIT_E_SET( ctl , BIT_E_DISABLE );
	HD44780_SEND_IR( ctl );
	DELAY_SHORT();
	
	HD44780_SET_DR_WRITED();

	return data;
}

INLINE void HD44780_INIT( volatile unsigned char * dr_p  
#ifdef HD44780_HAS_DDR
			  ,volatile unsigned char * dr_ddr_p
#endif
			  ,volatile unsigned char * ir_p 
#ifdef HD44780_HAS_DDR
			  ,volatile unsigned char * ir_ddr_p
#endif
){

	dr_addr_p 	= dr_p ;
	ir_addr_p 	= ir_p ;

#ifdef HD44780_HAS_DDR
	/* set the direction data reg */
	dr_ddr_addr_p	= dr_ddr_p;
	ir_ddr_addr_p	= ir_ddr_p;

	HD44780_SET_DR_WRITED();
	HD44780_SET_IR_WRITED();
#endif

	/* initialization of hd44780 */
	HD44780_FUNC_function_set( 1 , 1 , 0 );
	DELAY_LONG();
	HD44780_FUNC_function_set( 1 , 1 , 0 );
	HD44780_FUNC_function_set( 1 , 1 , 0 );

	/* start to set the function mode of hd44780 */
	HD44780_FUNC_function_set( 1 , 1 , 0 );

	HD44780_FUNC_display_on_off_control( 1 , 0 , 0 );
	
	HD44780_FUNC_clear_display();

	HD44780_FUNC_entry_mode_set( 1 , 0 );

	return;
}

#endif
